﻿
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace S7Communication
{
    public class VariablesManager
    {
        /// <summary>
        /// 变量管理器
        /// </summary>
        /// <param name="plc_type">涉及到最大收发数据编组</param>
        /// <param name="isReadOrWrite">涉及到最大收发数据编组</param>
        public VariablesManager(PLC_TYPE plc_type, bool isReadOrWrite)
        {
            if (plc_type == PLC_TYPE.S7_1200) { maxPduLength = 480; }
            else if (plc_type == PLC_TYPE.S7_1500) { maxPduLength = 960; }
            pduLength = 18;
            this.isReadOrWrite = isReadOrWrite;
        }
        private Dictionary<string, Variable> variables = new Dictionary<string, Variable>();
        private List<List<Variable>> variablesGroups = new List<List<Variable>>();
        private int pduLength;
        private int maxPduLength;
        private bool isReadOrWrite;
        object locker = new object();
        public object? Read(string name, bool result)
        {
            try
            {
                Variable variable = variables[name];
                if (variable.dateType == VARIABLE_TYPE.Bool)
                {
                    if (variable.Count == 1)
                    {
                        return variable.data[0] == 1;
                    }
                    else
                    {
                        bool[] datas = new bool[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = variable.data[i] == 1;
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Byte)
                {
                    if (variable.Count == 1)
                    {
                        return variable.data[0];
                    }
                    else
                    {
                        byte[] datas = new byte[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = variable.data[i];
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Word || variable.dateType == VARIABLE_TYPE.Int)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToInt16(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        short[] datas = new short[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToInt16(variable.data.Skip(i * 2).Take(2).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.UInt)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToInt16(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        ushort[] datas = new ushort[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToUInt16(variable.data.Skip(i * 2).Take(2).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.DWord || variable.dateType == VARIABLE_TYPE.DInt)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToInt32(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        int[] datas = new int[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToInt32(variable.data.Skip(i * 4).Take(4).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.UDInt)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToUInt32(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        uint[] datas = new uint[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToUInt32(variable.data.Skip(i * 4).Take(4).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Real)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToSingle(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        float[] datas = new float[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToSingle(variable.data.Skip(i * 4).Take(4).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.ULInt)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToUInt64(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        UInt64[] datas = new UInt64[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToUInt64(variable.data.Skip(i * 8).Take(8).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.LInt)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToInt64(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        Int64[] datas = new Int64[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToInt64(variable.data.Skip(i * 8).Take(8).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.LReal)
                {
                    if (variable.Count == 1)
                    {
                        return BitConverter.ToDouble(variable.data.Reverse().ToArray());
                    }
                    else
                    {
                        double[] datas = new double[variable.Count];
                        for (int i = 0; i < datas.Length - 1; i++)
                        {
                            datas[i] = BitConverter.ToDouble(variable.data.Skip(i * 8).Take(8).Reverse().ToArray());
                        }
                        return datas;
                    }
                }
                else throw new Exception("读取错误/数据类型错误");

            }
            catch (Exception ex)
            {
                throw new Exception("读取错误", ex);
            }
        }
        public void Write(string name, object value)
        {
            Write(name, new object[1] { value });
        }
        /// <summary>
        /// 写入值
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="value">变量数组值</param>
        /// <returns>ture：写入成功，不代表发送成功，false：写入失败</returns>
        public void Write(string name, object[] value)
        {
            try
            {
                Variable variable = variables[name];
                if (variable.dateType == VARIABLE_TYPE.Bool)
                {
                    if (value.Length != variable.Count) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        variable.data[i] = (bool)(value[i]) ? (byte)1 : (byte)0;
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Byte)
                {
                    if (value.Length != variable.Count) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        variable.data[i] = (byte)value[i];
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Word || variable.dateType == VARIABLE_TYPE.Int)
                {
                    if (value.Length != variable.Count * 2) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((Int16)value[i]).Reverse().ToArray(), 0, variable.data, i * 2, 2);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.UInt)
                {
                    if (value.Length != variable.Count * 2) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((UInt16)value[i]).Reverse().ToArray(), 0, variable.data, i * 2, 2);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.DWord || variable.dateType == VARIABLE_TYPE.DInt)
                {
                    if (value.Length != variable.Count * 4) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((Int32)value[i]).Reverse().ToArray(), 0, variable.data, i * 4, 4);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.UDInt)
                {
                    if (value.Length != variable.Count * 4) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((UInt32)value[i]).Reverse().ToArray(), 0, variable.data, i * 4, 4);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.Real)
                {
                    if (value.Length != variable.Count * 4) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((float)value[i]).Reverse().ToArray(), 0, variable.data, i * 4, 4);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.LInt)
                {
                    if (value.Length != variable.Count * 8) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((Int64)value[i]).Reverse().ToArray(), 0, variable.data, i * 8, 8);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.ULInt)
                {
                    if (value.Length != variable.Count * 8) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((UInt64)value[i]).Reverse().ToArray(), 0, variable.data, i * 8, 8);
                    }
                }
                else if (variable.dateType == VARIABLE_TYPE.LReal)
                {
                    if (value.Length != variable.Count * 8) { throw new Exception("写入数组长度错误"); }
                    for (int i = 0; i < variable.Count - 1; i++)
                    {
                        Array.Copy(BitConverter.GetBytes((double)value[i]).Reverse().ToArray(), 0, variable.data, i * 8, 8);
                    }
                }
                variables[name] = variable;
            }
            catch (Exception e)
            {
                throw new Exception("写入错误", e);
            }
        }
        /// <summary>
        /// 加载请求变量组
        /// </summary>
        /// <param name="variablesGroups"></param>
        public void Load(out List<List<Variable>> variablesGroups)
        {
            lock (locker)
            {
                variablesGroups = this.variablesGroups;
            }
        }
        /// <summary>
        /// 更新返回变量结果
        /// </summary>
        /// <param name="name"></param>
        /// <param name="data"></param>
        /// <param name="returnCode"></param>
        public void Updata(string name, byte[] data, DT_RETRUN_CODE returnCode)
        {
            Variable variable = variables[name];
            variable.data = data;
            variable.returnCode = returnCode;
            variables[name] = variable;
        }
        /// <summary>
        /// 更新返回变量结果
        /// </summary>
        /// <param name="name"></param>
        /// <param name="returnCode"></param>
        public void Updata(string name, DT_RETRUN_CODE returnCode)
        {
            Variable variable = variables[name];
            variable.returnCode = returnCode;
            variables[name] = variable;
        }
        /// <summary>
        /// 添加变量
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="adrress">地址，字符大写，例如：DB10.DBX10.0，DB10.DBB10，DB10.DBW10,DB10.DBD10，M10.0，I10.0，Q10.0</param>
        /// <param name="dateType">数据类型</param>
        /// <param name="count">数量=1为单个变量，大于1为数组</param>
        /// <exception cref="Exception"></exception>
        public void AddVariable(string name, string adrress, VARIABLE_TYPE dateType, int count)
        {
            lock (locker)
            {
                Variable variable;
                try { CompileVariable(name, adrress, dateType, count, out variable); } catch (Exception e) { throw new Exception("编译变量/" + e.Message); }
                if (variables.ContainsKey(variable.name)) { throw new Exception("添加托管变量/变量已存在"); }
                try { VariablesGroup(variable); } catch (Exception e) { throw new Exception("变量排序错误/" + e.Message); }
                try { variables.Add(variable.name, variable); } catch (Exception e) { throw new Exception("添加托管变量/" + e.Message); }
            }
        }
        /// <summary>
        /// 数据编组
        /// </summary>
        /// <param name="varialbe">增加变量</param>
        private void VariablesGroup(Variable varialbe)
        {

            ushort itemLength = 0;
            List<Variable> variableList = variablesGroups.Last();
            //计算item长度
            if (isReadOrWrite)
            {
                itemLength = (ushort)(varialbe.dataLength + 4);
                if (itemLength < 12) itemLength = 12;
            }
            else
            {
                itemLength = (ushort)(varialbe.dataLength + 12);
            }
            if (pduLength + varialbe.dataLength < maxPduLength)
            {
                //更新pdu长度
                pduLength += itemLength;
                //编组变量
                variableList.Add(varialbe);
                //更新item组分组
                variablesGroups[variablesGroups.Count - 1] = variableList;
            }
            else
            {
                //初始化PDU长度
                pduLength = 18;
                //计算item长度
                if (isReadOrWrite)
                {
                    itemLength = (ushort)(varialbe.dataLength + 4);
                    if (itemLength < 12) itemLength = 12;
                }
                else
                {
                    itemLength = (ushort)(varialbe.dataLength + 12);
                }
                //更新pdu长度
                pduLength += itemLength;
                //清除item组
                variableList.Clear();
                //增加item
                variableList.Add(varialbe);
                //增加item组分组
                variablesGroups.Add(variableList);
            }
        }
        /// <summary>
        /// 编译变量
        /// </summary> 
        /// <param name="name">变量名称</param>
        /// <param name="adrress">地址，字符大写，例如：DB10.DBX10.0，DB10.DBB10，DB10.DBW10,DB10.DBD10，M10.0，I10.0，Q10.0</param>
        /// <param name="dateType">数据类型</param>
        /// <param name="count">数量=1为单个变量，大于1为数组</param>
        /// <param name="variable">返回编译变量</param>
        /// <exception cref="Exception"></exception>
        private void CompileVariable(string name, string adrress, VARIABLE_TYPE dateType, int count, out Variable variable)
        {
            variable = new Variable();
            //确定数据长度
            if (dateType == VARIABLE_TYPE.Bool)
            {
                if (count <= 1) variable.dataLength = 1;
                else variable.dataLength = (ushort)count;
                variable.data = new byte[variable.dataLength];
            }
            else if (dateType == VARIABLE_TYPE.Byte)
            {
                if (count <= 1) variable.dataLength = 1;
                else variable.dataLength = (ushort)count;
                variable.data = new byte[variable.dataLength];
            }
            else if (dateType == VARIABLE_TYPE.Word || dateType == VARIABLE_TYPE.Int || dateType == VARIABLE_TYPE.UInt)
            {
                if (count <= 1) variable.dataLength = 2;
                else variable.dataLength = (ushort)(count * 2);
                variable.data = new byte[variable.dataLength];
            }
            else if (dateType == VARIABLE_TYPE.DWord || dateType == VARIABLE_TYPE.DInt || dateType == VARIABLE_TYPE.UDInt || dateType == VARIABLE_TYPE.Real)
            {
                if (count <= 1) variable.dataLength = 4;
                else variable.dataLength = (ushort)(count * 4);
                variable.data = new byte[variable.dataLength];
            }
            else if (dateType == VARIABLE_TYPE.LInt || dateType == VARIABLE_TYPE.ULInt || dateType == VARIABLE_TYPE.LReal)
            {
                if (count <= 1) variable.dataLength = 8;
                else variable.dataLength = (ushort)(count * 8);
                variable.data = new byte[variable.dataLength];
            }
            if (name == null) { throw new Exception(nameof(name) + "：为空"); }
            if (adrress == null) { throw new Exception(nameof(adrress) + "：为空"); }
            variable.name = name;
            variable.specificationType = 0x12;
            variable.itemLength = 0x0a;
            variable.syntaxId = DT_DATA_SYNTAX_ID.S7ANY;
            int byteOffset;
            int bitOffset;
            try
            {
                string[] strings = adrress.Split(".");
                //解析地址      
                if (strings[0].Substring(0, 1) == "M" || strings[0].Substring(0, 1) == "I" || strings[0].Substring(0, 1) == "Q")
                {
                    variable.dbNumber = 0;
                    if (strings[0].Substring(0, 1) == "M")
                    {
                        variable.area = DT_DATA_AREA.Flags;
                    }
                    else if (strings[0].Substring(0, 1) == "I")
                    {
                        variable.area = DT_DATA_AREA.Inputs;
                    }
                    else if (strings[0].Substring(0, 1) == "Q")
                    {
                        variable.area = DT_DATA_AREA.Outputs;
                    }
                    try
                    {
                        //地址偏移
                        try { byteOffset = ushort.Parse(strings[0].Remove(0, 1)); } catch { throw new Exception("字节偏移错误"); }
                        try { bitOffset = ushort.Parse(strings[1]); }
                        catch { if (dateType == VARIABLE_TYPE.Bool) { throw new Exception("位偏移错误"); } else { bitOffset = 0; } }
                        if (bitOffset < 0 || bitOffset > 7) { throw new Exception("位偏移错误"); }
                        if (bitOffset != 0 && dateType != VARIABLE_TYPE.Bool) { throw new Exception("位偏移与数据类型不匹配"); }
                        //数据类型和长度
                        if (dateType == VARIABLE_TYPE.Bool)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.Bit;
                        }
                        else if (dateType == VARIABLE_TYPE.Byte)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else if (dateType == VARIABLE_TYPE.Word || dateType == VARIABLE_TYPE.Int || dateType == VARIABLE_TYPE.UInt)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else if (dateType == VARIABLE_TYPE.DWord || dateType == VARIABLE_TYPE.DInt || dateType == VARIABLE_TYPE.UDInt || dateType == VARIABLE_TYPE.Real)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else if (dateType == VARIABLE_TYPE.LInt || dateType == VARIABLE_TYPE.ULInt || dateType == VARIABLE_TYPE.LReal)
                        {
                            throw new Exception("该地址类型不支持64位数据类型");
                        }
                        else { throw new Exception("数据类型未知"); }
                    }
                    catch { throw new Exception("偏移类型错误"); }
                }
                else if (strings[0].Substring(0, 2) == "DB")
                {
                    variable.area = DT_DATA_AREA.DataBlocks;
                    try { variable.dbNumber = ushort.Parse(strings[0].Remove(0, 2)); } catch { throw new Exception("DB号错误"); }
                    try
                    {
                        //偏移解析
                        if (strings[1].Substring(0, 3) == "DBX")
                        {
                            try { byteOffset = ushort.Parse(strings[1].Remove(0, 3)); } catch { throw new Exception("字节偏移错误"); }
                            try { bitOffset = ushort.Parse(strings[2]); } catch { throw new Exception("位偏移错误"); }
                            if (bitOffset < 0 || bitOffset > 7) { throw new Exception("位偏移错误"); }
                            if (bitOffset != 0 && dateType != VARIABLE_TYPE.Bool) { throw new Exception("地址类型与数据类型不匹配"); }
                        }
                        else if (strings[1].Substring(0, 3) == "DBB" || strings[1].Substring(0, 3) == "DBW" || strings[1].Substring(0, 3) == "DBD")
                        {
                            try { byteOffset = ushort.Parse(strings[1].Remove(0, 3)); } catch { throw new Exception("DBX字节偏移错误"); }
                            if (dateType == VARIABLE_TYPE.Bool) { throw new Exception("地址类型与数据类型不匹配"); }
                            bitOffset = 0;
                        }
                        else { throw new Exception("DB数据类型错误"); }
                        //类型解析
                        if (dateType == VARIABLE_TYPE.Bool)
                        {
                            try { bitOffset = ushort.Parse(strings[1]); } catch { throw new Exception("位偏移错误"); }
                            if (bitOffset < 0 || bitOffset > 7) { throw new Exception("位偏移错误"); }
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.Bit;
                        }
                        else if (dateType == VARIABLE_TYPE.Byte)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;

                        }
                        else if (dateType == VARIABLE_TYPE.Word || dateType == VARIABLE_TYPE.Int || dateType == VARIABLE_TYPE.UInt)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else if (dateType == VARIABLE_TYPE.DWord || dateType == VARIABLE_TYPE.DInt || dateType == VARIABLE_TYPE.UDInt || dateType == VARIABLE_TYPE.Real)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else if (dateType == VARIABLE_TYPE.LInt || dateType == VARIABLE_TYPE.ULInt || dateType == VARIABLE_TYPE.LReal)
                        {
                            variable.transportSizes = DT_DATA_TRANSPORT_SIZI.BYTE;
                        }
                        else { throw new Exception("数据类型未知"); }
                    }
                    catch { throw new Exception("DB数据类型错误"); }
                }
                else { throw new Exception("地址类型错误"); }
            }
            catch { throw new Exception("地址类型错误"); }
            variable.adrress = byteOffset * 8 + bitOffset;
            if (variable.dataLength > maxPduLength - 22) { throw new Exception("数据长度过长"); }
        }
    }

}
